#!/usr/bin/env python
# -*- mode: python; coding: utf-8; -*-
##--------------------------------------------------------------------------##
##
## Copyright (C) 1998-2003 Markus Oberhumer <markus@oberhumer.com>
## Copyright (C) 2003 Mt. Hood Playing Card Co.
## Copyright (C) 2005-2010 Skomoroh <skomoroh@users.sourceforge.net>
## Copyright (C) 2010-2013 Jeremy Austin-Bardo <tjaustinbardo@gmail.com>
##
## This program is free software: you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published by
## the Free Software Foundation, either version 3 of the License.
##
## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
## GNU General Public License for more details.
##
## You should have received a copy of the GNU General Public License
## along with this program.  If not, see <http://www.gnu.org/licenses/>.
##
##--------------------------------------------------------------------------##
__all__ = []

from collections import namedtuple
import logging

Logger = logging.getLogger('PySolFC.LayoutCards')

canvasSize = namedtuple('CanvasSize', ('width', 'height'))


class _LayoutStack(object):
    """
    a layout stack
    """
    __slots__ = ['x', 'y', 'suit', 'ncards', 'rounds', 'text', 'offset']

    def __init__(self, x, y, suit=None):
        self.x = int(x)
        self.y = int(y)
        self.offset = (0, 0,)
        self.suit = suit
        self.ncards = {}
        self.rounds = {}
        self.text = {}

    def set_ncards(self, anchor='center', **kw):
        self.ncards = kw
        self.ncards['anchor'] = anchor

    def set_rounds(self, anchor='center', **kw):
        self.rounds = kw
        self.rounds['anchor'] = anchor
        self.rounds['always'] = False

    def set_text(self, anchor='center', **kw):
        self.text = kw
        self.text['anchor'] = anchor

    def set_offset(self, *args):
        self.offset = tuple(args)


class CardLayout(object):

    def _create_stack(self, x, y, suit=None):
        """
        _create_stack(x:int, y:int, suit:int=None) -> obj

        Create layout stack with x, y coords and suit and add coords to layout
        stackmap.

        :param integer x: left x-coordinate
        :param integer y: top y-coordinate
        :keyword integer suit: base suit for stack
        :return: layout stack

        seealso::
          _get_group_regions() for stackmap usage.
        """
        stack = _LayoutStack(x, y, suit)
        mapkey = (stack.x, stack.y)
        assert mapkey not in self.stackmap
        self.stackmap[mapkey] = stack
        return stack

    def _get_stack_height(self, playcards, height=None):
        """
        _get_stack_height(playcards:int, height:int=2 * CH / 3) -> int

        Compute height of a stack with playcards so at least height of a card
        is visible. Default is 2/3 card height.

        :param integer playcards: num possible cards for stack.
        :keyword integer height: height of visible card.
        :return: integer of total height for all playcards and visible card.
        """
        height = 2 * self.CH // 3 if height is None else height
        return height + ((playcards - 1) * self.YOFFSET if playcards > 0 else 0)

    def _get_stack_width(self, playcards, width=None):
        """
        _get_stack_width(playcards:int, width:int=XS) -> int

        Compute width of a stack with playcards so at least width of a card
        is visible. Default is card with plus margin.

        :param integer playcards: num possible cards for stack.
        :keyword integer width: width of visible card.
        :return: integer of total width for all playcards and visible card.
        """
        width = self.XS if width is None else width
        return width + ((playcards - 1) * self.XOFFSET if playcards > 0 else 0)

    def _get_row_size(self, piles, max_):
        """
        _get_row_size(piles:int, max_:int) -> tup

        Split piles into a sequence max_ values and remainder.

        :param integer piles: num piles total.
        :param integer max_: max_ piles in a direction.
        :return: seqeunce of values equal to piles.
        """
        rc, rr = divmod(piles, max_)
        rows = (max_,) * int(rc)
        if rr > 0:
            rows += (rr,)
        return rows

    def _get_xoffset(self, size, ncards=None):
        ncards = ncards or self.NCARDS
        if self._get_stack_width(ncards) >= size:
            return size // ncards
        else:
            return self.XOFFSET

    def _get_stack_offset(self, **kw):
        """
        _get_row_size([xcards:int | ycards:int]) -> tup

        Return an offset for a stack of cards.

        :keyword xcards int: possible horz cards.
        :keyword ycards int: possible vert cards.
        :return: offset as horz, vert tuple.
        :raises: AssertionError if both keywords set.
        """
        assert not all(kw.get(key, 0) > 0 for key in ('xcards', 'ycards',))
        if kw.get('ycards', 0) > 0:
            return (0, self.YOFFSET,)
        elif kw.get('xcards', 0) > 0:
            return (self.XOFFSET, 0,)
        return (0, 0,)

    def delta_coords(self, x=None, y=None, dx=0, dy=0, fx=1, fy=1):
        """
        delta_coords(x:int=XM, y:int=YM, dx:int=0, dy:int=0, fx:int=1,
          fy:int=1) -> tup

        :keyword integer x: left x-coordinate.
        :keyword integer y: top y-coordinate.
        :keyword integer dx: delta x-offset.
        :keyword integer dy: delta y-offset.
        :keyword integer dx: fraction to divide x value.
        :keyword integer dy: fraction to divide y value.
        :return: tuple of x, y coordinates.
        """
        x, y = x or self.XM, y or self.YM
        x += dx * self.XS // fx
        y += dy * self.YS // fy
        w, h = self.size
        self.size = canvasSize(max(w, x + self.XS), max(h, y + self.YS,))
        return (x, y,)

    def row_iter(self, x, npiles, dx=None):
        """
        row_iter(x:int, npiles, dx:int=XS) -> iter

        Iterate row of x coords for npiles with dx or XS offset.

        :param integer x: init x-coordinate.
        :param integer npiles: num piles.
        :keyword integer dx: delta x-offset.
        :return: generator of x-coordinates for each pile.
        """
        dx = dx if dx is not None else self.XS
        for x_ in range(int(x), int(x + npiles * dx), int(dx)):
            yield x_
        w, h = self.size
        self.size = canvasSize(max(w, x_ + dx), h,)

    def col_iter(self, y, npiles, dy=None):
        """
        col_iter(y:int, npiles, dy:int=YS) -> None

        Iterate col of y coords for npiles with dy or YS offset.

        :param integer y: init y-coordinate.
        :param integer npiles: num piles.
        :keyword integer dy: delta y-offset.
        :return: generator of y-coordinates for each pile.
        """
        dy = dy if dy is not None else self.YS
        for y_ in range(y, y + npiles * dy, dy):
            yield y_
        w, h = self.size
        self.size = canvasSize(w, max(h, y_ + dy),)

    def round_iter(self, npiles, dx=None, dy=None):
        """
        round_iter(npiles[, dx:int=XM, dy:int=YM]) -> None

        Iterate circumference of x, y coords for npiles from dy, dy.

        :param integer npiles: num piles.
        :keyword integer dx: initial x coordinte.
        :keyword integer dy: initial y coordinte.
        """
        from math import sin, cos, pi
        dx = dx if dx is not None else self.XM
        dy = dy if dy is not None else self.YM
        arc = pi * 2 // npiles
        xo, yo = self.XM * self.XS
        x_, y_ = 0, 0
        for x, y in ((int(sin(pile * arc) * self.XM) * self.XS + xo,
                      int(cos(pile * arc) * self.YM) * self.YS + yo,)
                      for pile in range(0, npiles)):
            x_, y_ = max(x_, x), max(y_, y)
            yield x, y
        w, h = self.size
        self.size = canvasSize(max(w, x_ + self.XS), max(h, y_ + self.YS,))

    def col_block_iter(self, x, y, npiles, ncols, xcards=0, ycards=0):
        """
        col_block_iter(x:int, y:int, npiles:int, ncols:int[xcards:int=0,
          ycards:int=0]) -> None

        Iterate block by col of x, y coords for npiles over ncols.

        :param integer x: init x-coordinate.
        :param integer y: init y-coordinate.
        :param integer npiles: num piles total.
        :param integer ncols: num piles horz.
        :keyword integer xcards: # cards offset horz on pile.
        :keyword integer ycards: # cards offset vert on pile.
        """
        assert not all(cards > 0 for cards in (xcards, ycards,))
        dx, dy = None, None
        if xcards > 0:
            dx = self._get_stack_width(xcards)
        elif ycards > 0:
            dy = self._get_stack_height(ycards)
        rows = self._get_row_size(npiles, ncols)
        for y_, row in zip(self.col_iter(y, len(rows), dy), rows):
            for x_ in self.row_iter(x, row, dx):
                yield (x_, y_,)

    def row_block_iter(self, x, y, npiles, nrows, xcards=0, ycards=0):
        """
        row_block_iter(x:int, y:int, npiles:int, nrows:int[xcards:int=0,
          ycards:int=0]) -> None

        Iterate block by row of x, y coords for npiles over nrows.

        :param integer x: init x-coordinate.
        :param integer y: init y-coordinate.
        :param integer npiles: num piles total.
        :param integer nrows: num piles vert.
        :keyword integer xcards: # cards offset horz on pile.
        :keyword integer ycards: # cards offset vert on pile.
        """
        assert not all(cards > 0 for cards in (xcards, ycards,))
        dx, dy = None, None
        if xcards > 0:
            dx = self._get_stack_width(xcards)
        elif ycards > 0:
            dy = self._get_stack_height(ycards)
        rows = self._get_row_size(npiles, nrows)
        for x_, row in zip(self.row_iter(x, len(rows), dy), rows):
            for y_ in self.col_iter(y, row, dx):
                yield (x_, y_,)